from __future__ import annotations

"""
认证助手（JWT/密码/2FA）
-----------------------
职责：
- 口令哈希与校验（bcrypt）
- JWT 生成/解析（区分 access/refresh）
- 简单 2FA 验证（可开关）
"""

import datetime as dt
from typing import Any, Dict, Optional

from jose import JWTError, jwt
from passlib.context import CryptContext

from ..config import get_settings

settings = get_settings()

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


class AuthError(Exception):
    """认证失败时抛出。"""


def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)


def _now_utc() -> dt.datetime:
    """当前 UTC 时间。"""
    return dt.datetime.now(tz=dt.timezone.utc)


def create_access_token(data: Dict[str, Any]) -> str:
    expire = _now_utc() + dt.timedelta(minutes=settings.jwt_access_minutes)
    payload = data.copy()
    payload.update({"exp": expire, "type": "access"})
    return jwt.encode(payload, settings.jwt_secret, algorithm="HS256")


def create_refresh_token(data: Dict[str, Any]) -> str:
    expire = _now_utc() + dt.timedelta(days=settings.jwt_refresh_days)
    payload = data.copy()
    payload.update({"exp": expire, "type": "refresh"})
    return jwt.encode(payload, settings.jwt_secret, algorithm="HS256")


def decode_token(token: str, expected_type: str | None = None) -> Dict[str, Any]:
    try:
        payload = jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])
    except JWTError as exc:
        raise AuthError("Invalid token") from exc
    token_type = payload.get("type")
    if expected_type and token_type != expected_type:
        raise AuthError("Invalid token type")
    return payload


def create_token_pair(subject: str, extra_claims: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
    claims = {"sub": subject}
    if extra_claims:
        claims.update(extra_claims)
    access_token = create_access_token(claims)
    refresh_token = create_refresh_token(claims)
    return {"access_token": access_token, "refresh_token": refresh_token}


def verify_otp(email: str, otp_code: Optional[str]) -> bool:
    if not settings.two_factor_enabled:
        return True
    if not otp_code:
        return False
    return otp_code == settings.two_factor_static_code
